// Control the game process and logic.
public class Game {
    private final Map map;
    private final Character monster, rogue;
    private final int maxStep;
    private int step;
    private AI monsterAI = null;
    private AI rogueAI = null;
    private int pause = -1;

    public Game(String dungeonName, int maxStep) {
        Input reader = new Input(dungeonName);
        if (dungeonName == null) System.out.println("\nPaste your dungeon at below:");
        else System.out.println("\nLoad " + dungeonName + ":");

        char[] monsterName = new char[1];
        int[] initialPositions = new int[4]; // {monsterX, monsterY, rogueX, rogueY}
        map = new Map(reader, initialPositions, monsterName);
        monster = new Character(initialPositions[0], initialPositions[1], monsterName[0]);
        rogue = new Character(initialPositions[2], initialPositions[3], '@');
        step = 0;
        this.maxStep = maxStep;
        System.out.println(this);
        reader.close();
    }

    public int start() {
        Input reader = new Input(null);

        if(monsterAI == null || rogueAI == null)
            System.out.println("User control:\n" + "Q   W   E\n"+"  \\ ^ /\n"+"A < S > D\n"+"  / v \\\n"+"Z   X   C");

        while (true) {
            if ((++step) % 2 == 1) { // Monster moves
                if (monsterAI == null) { // user control
                    System.out.println("\nStep: " + step + "\nYour turn to control the monster.");
                    int[] move = getUserMove(monster.getPos(), reader);
                    monster.move(move);
                } else { // AI control
                    if (rogueAI == null || pause >= 0) sleep(pause);
                    int[] move = monsterAI.move();
                    monster.move(move);
                    System.out.println("\nStep: " + step + "\nMonster moved.");
                }
            } else { // Rogue moves
                if (rogueAI == null) { // user control
                    System.out.println("\nStep: " + step + "\nYour turn to control the rogue.");
                    int[] move = getUserMove(rogue.getPos(), reader);
                    rogue.move(move);
                } else { // AI control
                    if (monsterAI == null || pause >= 0) sleep(pause);
                    int[] move = rogueAI.move();
                    rogue.move(move);
                    System.out.println("\nStep: " + step + "\nRogue moved.");
                }
            }

            System.out.println(this);

            if (ifMonsterWin()) {
                System.out.println("Monster wins! Rogue is caught after steps: " + step);
                break;
            }
            if (ifRogueWin()) {
                System.out.println("Rogue wins! Max number of steps reached: " + step);
                break;
            }
        }
        reader.close();
        return step;
    }

    // Random, TowardChaste, AStarManhattan, ShortestOrthogonalDistance
    public void setMonsterAI(String s) {
        switch (s) {
            case "Random":
                monsterAI = new MonsterAI(map, this);
                break;
            case "TowardChaste":
                monsterAI = new TowardChaste(map, this);
                break;
            case "AStarManhattan":
                monsterAI = new AStarManhattan(map, this);
                break;
            case "AStarChebyshev":
                monsterAI = new AStarChebyshev(map, this);
                break;
            case "AStarHeuristic":
                monsterAI = new AStarHeuristic(map, this);
                break;
            default:
                System.out.println("No matched monster AI.");
                return;
        }
        System.out.println("Monster uses strategy: " + s);
    }

    // Random, RunAway, LoopElseFurthest, GoFurthest
    public void setRogueAI(String s) {
        switch (s) {
            case "Random":
                rogueAI = new RogueAI(map, this);
                break;
            case "RunAway":
                rogueAI = new RunAway(map, this);
                break;
            case "LoopElseFurthest":
                rogueAI = new LoopElseFurthest(map, this);
                break;
            case "GoFurthest":
                rogueAI = new GoFurthest(map, this);
                break;
            default:
                System.out.println("No matched monster AI.");
                return;
        }
        System.out.println("Rogue uses strategy: " + s);
    }

    public void setPause(int millis) {
        pause = millis;
    }

    public int[] getMonsterPos() {
        return monster.getPos();
    }

    public int[] getRoguePos() {
        return rogue.getPos();
    }

    public boolean ifMonsterWin() {
        return monster.compareTo(rogue) == 0;
    }

    public boolean ifRogueWin() {
        return step >= maxStep;
    }

    // Use keyboard to control movement intuitively.
    // right: x = 1, left: x = -1, up: y = 1, down: y = -1
    public int[] getUserMove(int[] position, Input reader) {
        while (true) {
            char c = reader.readChar();
            int[] result = null;
            switch (c) {
                case 'q':
                    result = new int[]{-1, 1};
                    break;
                case 'w':
                    result = new int[]{0, 1};
                    break;
                case 'e':
                    result = new int[]{1, 1};
                    break;
                case 'a':
                    result = new int[]{-1, 0};
                    break;
                case 's':
                    result = new int[]{0, 0};
                    break;
                case 'd':
                    result = new int[]{1, 0};
                    break;
                case 'z':
                    result = new int[]{-1, -1};
                    break;
                case 'x':
                    result = new int[]{0, -1};
                    break;
                case 'c':
                    result = new int[]{1, -1};
                    break;
                default:
                    System.out.println("Illegal move, try again.");
                    continue;
            }
            if (!map.isDirectionMoveable(position, result)) System.out.println("Can't reach, try again.");
            else return result;
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (int y = map.size() - 1; y >= 0; y--) {
            for (int x = 0; x < 2 * map.size() - 1; x++) {
                if (x % 2 == 0) {
                    if (x / 2 == monster.getX() && y == monster.getY()) {
                        sb.append(monster);
                    } else if (x / 2 == rogue.getX() && y == rogue.getY()) {
                        sb.append(rogue);
                    } else sb.append(map.getType(x / 2, y));
                } else {
                    sb.append(" ");
                }
            }
            sb.append(System.lineSeparator());
        }
        return sb.toString();
    }

    public void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
